package com.yb0os1.CglibProxy;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.lang.reflect.Method;
import java.util.Arrays;

public class MyProxyFactory {

    //  创建代理对象
    public static <T> T createProxy(T target) throws Exception {
        //获取一个class文件 代理对象的
        Class proxyClz = createProxyClass(target);
        //返回代理类的实例
        return (T) proxyClz.getConstructor(target.getClass()).newInstance(target);
    }

    private static <T> Class createProxyClass(T target) {
        // 获取目标对象的class
        Class<?> targetClass = target.getClass();
        //我们使用asm直接操作字节码文件
        //定义一个class的写入流
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        //通过targetClass文件获取类的版本
        // 使用读取到的版本号来定义新的类
        String name = targetClass.getName() + "$$Proxy";
        cw.visit(Opcodes.V17, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, name.replaceAll("\\.", "/"), null,
                Type.getInternalName(targetClass), null);

        //生成字段
        FieldVisitor field = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "target", Type.getDescriptor(targetClass), null, null);
        field.visitEnd();

        //构造函数相关
        {
            //生成构造方法
            MethodVisitor methodVisitor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
                    Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(targetClass)), null, null);
            methodVisitor.visitCode();

            //调用父类的构造方法
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(targetClass),
                    "<init>", "()V", false);

            //给target赋值
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, name.replaceAll("\\.", "/"),
                    "target", Type.getDescriptor(targetClass));

            //返回值
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitInsn(Opcodes.RETURN);
            methodVisitor.visitEnd();
        }

        //继承方法的加强

            for (Method method : Arrays.stream(targetClass.getMethods())
                    .filter(m -> m.getDeclaringClass() != Object.class)
                    .toArray(Method[]::new)) {

                MethodVisitor mv = cw.visitMethod(method.getModifiers(), method.getName(),
                        Type.getMethodDescriptor(method), null, null);

                mv.visitCode();

                // 打印日志
                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("方法被调用了：" + method.getName());
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

                // 调用目标方法
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitFieldInsn(Opcodes.GETFIELD, name.replaceAll("\\.", "/"), "target", Type.getDescriptor(targetClass));
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(targetClass),
                        method.getName(), Type.getMethodDescriptor(method), false);

                // 返回语句
                Type returnType = Type.getReturnType(Type.getMethodDescriptor(method));
                switch (returnType.getSort()) {
                    case Type.VOID: mv.visitInsn(Opcodes.RETURN); break;
                    case Type.BOOLEAN:
                    case Type.BYTE:
                    case Type.CHAR:
                    case Type.SHORT:
                    case Type.INT: mv.visitInsn(Opcodes.IRETURN); break;
                    case Type.LONG: mv.visitInsn(Opcodes.LRETURN); break;
                    case Type.FLOAT: mv.visitInsn(Opcodes.FRETURN); break;
                    case Type.DOUBLE: mv.visitInsn(Opcodes.DRETURN); break;
                    default: mv.visitInsn(Opcodes.ARETURN);
                }

                mv.visitMaxs(2, 2);
                mv.visitEnd();
            }


        //获取字节码
        byte[] bytes = cw.toByteArray();
        Class<?> aClass = new Loader(targetClass.getClassLoader()).defineClass(name, bytes);
        return aClass;
    }

    static class Loader extends ClassLoader {
        public Loader(ClassLoader parent) {
            super(parent);
        }

        public Class<?> defineClass(String name, byte[] b) {
            return defineClass(name, b, 0, b.length);
        }
    }
}
